{ "cells": [ { "metadata": {}, "cell_type": "markdown", "source": [ "# Serial Communication Code Cards\n", "## Try me\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ffraile/computer_science_tutorials/blob/main/source/Data%20Manipulation/exercises/Serial%20Communication%20cards.ipynb)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/ffraile/computer_science_tutorials/main?labpath=source%2FData%20Manipulation%2Fexercises%2FSerial%20Communication%20cards.ipynb)\n", "\n", "## How to use\n", "- Each card mirrors an A4 classroom prompt. **Predict first** (or discuss), then run the cell to check.\n", "- Detective cards show a buggy idea in Markdown; the code cell shows a **fixed** version.\n", "- Keep explanations short and schematic (*what* → *why*).\n", "\n", "### Turn Gemini into a coding tutor (no direct answers)\n", "Paste this in your first chat with Gemini to keep it in “tutor mode”:\n", "\n", "```markdown\n", "You are a **coding tutor** for Python in Jupyter/Colab. Follow the **course motto** “do not give up learning.”\n", "\n", "### Role & Goals\n", "- Use **Socratic guidance** and **test-first thinking** to help me solve problems myself.\n", "- Help me read errors, reason about state, and make small, safe iterations.\n", "\n", "### Strict Rules\n", "1) **Do not** provide full working solutions or paste complete functions/programs.\n", " - You may show **tiny illustrative fragments (≤3 lines)** or **pseudo-code with TODOs**, but not a drop-in answer.\n", "2) Prefer **questions over answers**; offer **one small next step** at a time.\n", "3) When debugging, explain **what the traceback says**, give **2–3 hypotheses**, and propose the **smallest diff** in *plain English* first.\n", "4) Encourage **TDD**: ask me to write/assert a test, predict, run, and report outputs.\n", "5) Keep responses concise (≈120–150 words) unless I ask for a deeper explanation or code review.\n", "6) Ask me to **run code and share results**; adapt based on the output.\n", "7) If I request the full solution, remind me of the rules and offer a **higher-tier hint** instead.\n", "8) When I finalize an exercise, reinforce learning lessons and suggest additional exercises\n", "\n", "### Interaction Loop (use this structure)\n", "- **Restate goal:** what I’m trying to accomplish in one line.\n", "- **Diagnose:** key assumption to check or error to interpret.\n", "- **Hint (tiered):**\n", " - Tier 1: Conceptual nudge (no code).\n", " - Tier 2: Directed hint (identify line/construct to change).\n", " - Tier 3: Pseudo-code with TODOs or a **1–3 line** pattern (still not a full solution).\n", "- **Next action:** one concrete step for me to try now.\n", "- **Ask back:** what to run/paste (output, test result, or traceback).\n", "\n", "### When reviewing my code\n", "- Comment on **correctness, clarity, naming, and complexity (big-O)**.\n", "- Suggest **tests** I’m missing (boundaries, empty cases, error paths).\n", "\n", "### Safety & Ethics\n", "- No secrets or private data in prompts.\n", "- avoid library functions/APIs unless I ask.\n", "\n", "Stay in tutor mode for the whole session.\n", "```\n", "\n", "\n", "## Code Cards\n", "### Continous logging\n", "The following function is used in Python to log continous data from an Arduino device. Explain in your own words why each call to the function ```open``` is made with different modes ('w' and 'a+'). What would happen if you used 'a+' in both cases?\n", "\n", "```python\n", "def log_continuous(ser, out_path=\"arduino_log.csv\", period=1.0, sim_mode=False):\n", " \"\"\"\n", " Poll metrics every 'period' seconds and append to CSV.\n", " Stop with Ctrl+C.\n", " \"\"\"\n", " fields = [\"timestamp\", \"uptime_ms\", \"vcc_mv\", \"led\"]\n", " # Ensure output directory exists\n", " if not os.path.exists(os.path.dirname(out_path)):\n", " with open(out_path, 'w', newline='', encoding='utf-8') as csvfile:\n", " writer = csv.DictWriter(csvfile, fieldnames=fields)\n", " writer.writeheader()\n", " print(f\"📝 Logging to {out_path} every {period:.2f}s — press Ctrl+C to stop.\")\n", " try:\n", " with open(out_path, \"a+\", encoding=\"utf-8\") as f:\n", " while True:\n", " t0 = time.time()\n", " writer = csv.DictWriter(f, fieldnames=fields)\n", " row = read_metrics_once(ser, sim_mode=sim_mode)\n", " writer.writerow(row)\n", " f.flush()\n", " # Console status line\n", " print(row)\n", "\n", " # pacing delay:\n", " dt = time.time() - t0\n", " sleep_left = period - dt\n", " if sleep_left > 0:\n", " time.sleep(sleep_left)\n", " except KeyboardInterrupt:\n", " print(\"\\n⏹️ Logging stopped.\")\n", "```" ], "id": "39b0f849d7dba053" }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": "", "id": "4e87720ef38b5aaa" }, { "metadata": {}, "cell_type": "markdown", "source": [ "### Tone Keyboard\n", "Given the following Arduino board circuit connected to the computer via USB:\n", "\n", "![Arduino Tone Keyboard](img/tone_keyboard.png)\n", "\n", "This is the code that runs on the Arduino board:\n", "\n", "```cpp\n", "int pos = 0;\n", "\n", "void tone(int pin, int frequency, int duration) {\n", " // Calculate the period of the wave\n", " int period = 1000000 / frequency; // in microseconds\n", " int pulse = period / 2; // 50% duty cycle\n", "\n", " // Calculate the number of cycles to play\n", " int cycles = (frequency * duration) / 1000;\n", "\n", " for (int i = 0; i < cycles; i++) {\n", " digitalWrite(pin, HIGH);\n", " delayMicroseconds(pulse);\n", " digitalWrite(pin, LOW);\n", " delayMicroseconds(pulse);\n", " }\n", "}\n", "\n", "void setup()\n", "{\n", " pinMode(A0, INPUT);\n", " pinMode(8, OUTPUT);\n", " pinMode(A1, INPUT);\n", " pinMode(A2, INPUT);\n", "}\n", "\n", "void loop()\n", "{\n", " // if button press on A0 is detected\n", " if (digitalRead(A0) == HIGH) {\n", " tone(8, 440, 100);\n", " }\n", " // if button press on A1 is detected\n", " if (digitalRead(A1) == HIGH) {\n", " tone(8, 494, 100);\n", " }\n", " // if button press on A0 is detected\n", " if (digitalRead(A2) == HIGH) {\n", " tone(8, 523, 100);\n", " }\n", " //Check if serial data is available\n", " if (Serial.available() > 0) {\n", " note = Serial.read(); // read the incoming character\n", " switch (note) {\n", " case 'A':\n", " tone(8, 440, 100);\n", " break;\n", " case 'B':\n", " tone(8, 494, 100);\n", " break;\n", " case 'C':\n", " tone(8, 523, 100);\n", " break;\n", " }\n", " }\n", "}\n", "```\n", "\n", "And this is the Python code (You can try it in Google Colab or Jupyter Notebooks setting ```sim_mode = True```):\n", "\n", "```python\n", "import time\n", "import sys\n", "notes = [{'command': 'A', 'name': 'A4', 'tone': 57},\n", " {'command': 'B', 'name': 'B4', 'tone': 59},\n", " {'command': 'C', 'name': 'C5', 'tone': 60}]\n", "\n", "\n", "sim_mode = True\n", "if not sim_mode:\n", " import serial\n", "\n", "def print_menu():\n", " print(\"Available notes:\")\n", " for note in notes:\n", " print(f\"Press {note['command']} to play note {note['name']} (Tone {note['tone']})\")\n", "\n", " print(\"Press 0 to exit\")\n", "\n", "def main():\n", " # Set up serial connection (adjust 'COM3' and baudrate as needed)\n", " if not sim_mode:\n", " ser = serial.Serial('COM3', 9600, timeout=1)\n", " time.sleep(2) # Wait for the connection to establish\n", "\n", " while True:\n", " print_menu()\n", " choice = input(\"Enter your choice: \")\n", "\n", " if choice not in [note['command'] for note in notes] + ['0']:\n", " print(\"Invalid choice. Please try again.\")\n", " continue\n", "\n", " if choice == '0':\n", " print(\"Exiting...\")\n", " break\n", "\n", "\n", " for note in notes:\n", " if choice == note['command']:\n", " if not sim_mode:\n", " ser.write(note['command'].encode('utf-8'))\n", " print(f\"Playing note {note['name']} ({note['tone']})\")\n", " time.sleep(0.2) # Small delay to allow Arduino to process\n", "\n", " if not sim_mode:\n", " ser.close()\n", "\n", "if __name__ == \"__main__\":\n", " try:\n", " main()\n", " except KeyboardInterrupt:\n", " print(\"\\nStopped.\")\n", "```\n", "Answer the following questions:\n", "1. What is the sequence of frequencies played when the user inputs 'A', 'C', 'B' in that order?\n", "2. Why is comprehension used in the line ```if choice not in [note['command'] for note in notes] + ['0']:``?\n", "3. What changes would you make to the Python code to add a new note 'D5' with command 'D' (tone 61)? What needs to be added to the Arduino code to support this new note (frequency of new note is 587 Hz)?" ], "id": "9bceb519b1126122" }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": "", "id": "10d101da23811a05" }, { "metadata": {}, "cell_type": "markdown", "source": [ "### Smoothing average\n", "The following Arduino project connects a potentiometer, a simple mechanical device that provides a varying resistance based on the position of a knob. This basic set up is used to illustrate how smoothing averages can be computed from noisy sensor data. A smoothing average helps to reduce the effect of random fluctuations in the sensor readings, by averaging multiple readings over time.\n", "\n", "![Smoothing Average with Potentiometer](img/smoothing_average.png)\n", "\n", "The Arduino code reads the potentiometer value (an integer between 0 and 1023) and sends it over serial communication to a connected computer. The Python code reads these values, computes a smoothing average, and plots the results in real-time.\n", "\n", "This is the Arduino code:\n", "\n", "```cpp\n", "int inputPin = A0;\n", "\n", "void setup() {\n", " // initialize serial communication with computer:\n", " Serial.begin(9600);\n", "}\n", "\n", "void loop() {\n", " // read the potentiometer:\n", " int sensorValue = analogRead(inputPin);\n", " // send the value to the computer:\n", " Serial.print(\"pot_value: \");\n", " Serial.println(sensorValue);\n", " // wait 100 milliseconds before the next reading:\n", " delay(100);\n", "}\n", "```\n", "\n", "And this is the Python code (You can try it in Google Colab or Jupyter Notebooks setting ```sim_mode = True```):\n", "\n", "```python\n", "import time\n", "import matplotlib.pyplot as plt\n", "from IPython.display import display, clear_output\n", "\n", "\n", "sim_mode = True # Set to False when using real serial communication\n", "if not sim_mode:\n", " import serial\n", "\n", "def read_potentiometer(ser):\n", " if sim_mode:\n", " import random\n", " return random.randint(0, 3)*50 + 700 # Simulated noisy data around 700-850\n", " line = ser.readline().decode('utf-8').strip()\n", " if line.startswith(\"value:\"):\n", " value_str = line.split(\":\")[0].strip()\n", " return int(value_str)\n", " return None\n", "def main():\n", " if not sim_mode:\n", " ser = serial.Serial('COM3', 9600, timeout=1)\n", " else:\n", " ser = None\n", " time.sleep(2) # Wait for the connection to establish\n", " average_size = 10\n", " readings = [0 for i in range(average_size)]\n", " plt.ion() # Turn on interactive mode\n", " fig, ax = plt.subplots()\n", " line1, = ax.plot([], [], 'b-', label='Raw Data')\n", " line2, = ax.plot([], [], 'r-', label='Smoothing Average')\n", " ax.set_xlabel('measurement')\n", " ax.set_ylabel('Value')\n", " ax.set_xticks(range(10))\n", " ax.set_yticks(range(0, 1023, 50))\n", " ax.set_xlim(0, 9)\n", " ax.set_ylim(0, 1023)\n", " ax.legend()\n", " # show grid\n", "\n", " pos = 0\n", " while True:\n", " pot_value = read_potentiometer(ser)\n", " if pot_value is not None:\n", " readings[pos%average_size] = pot_value\n", " pos += 1\n", " smoothing_avg = sum(readings) / len(readings)\n", "\n", " # Update plot\n", " line1.set_xdata(range(len(readings)))\n", " line1.set_ydata(readings)\n", " line2.set_xdata(range(len(readings)))\n", " line2.set_ydata([smoothing_avg] * len(readings))\n", " clear_output(wait=True) # Clear previous output before redrawing (Jupyter specific)\n", " plt.draw() # Redraw the plot internally (Jupyter specific)\n", " display(fig) # Display the updated figure in the output cell (Jupyter specific)\n", " plt.pause(2)\n", " if not sim_mode:\n", " ser.close()\n", "if __name__ == \"__main__\":\n", " try:\n", " main()\n", " except KeyboardInterrupt:\n", " print(\"\\nStopped.\")\n", "```\n", "Answer the following questions:\n", "1. Fix the bugs in the `read_potentiometer` function so that it correctly extracts the potentiometer value from the serial input.\n", "2. Explain how the smoothing average is calculated in the Python code. What is the purpose of the `window_size` variable?\n", "3. The 11 first values of the potentiometer readings are ```850, 900, 950, 900, 850, 900, 950, 1000, 950, 900, 1000```. Use these values to manually compute the value of the ```readings``` list and the smoothing average after processing all 11 readings, and plot the expected output in the figure below.\n", "\n", "![Expected Smoothing Average Plot](img/smoothing_average_plot.png)\n", "\n", "3. Identify and explain any potential issues with the way the `readings` list is managed in the Python code. Hint: What happens when less than `window_size` readings are collected? Can you suggest a fix or alternative approach?" ], "id": "d348d047bf44e0e0" }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": "", "id": "e591d764cc1f6172" }, { "metadata": {}, "cell_type": "markdown", "source": [ "### Force Sensitive Resistor Color Mixer\n", "The following Arduino project connects 3 force sensitive resistors (FSRs) to an Arduino board. The FSRs are used to measure the force applied to them, which is then sent over serial communication to a connected computer. The Python code reads these values and creates an image, using the readings of each force (value from 0 to 1023) to encode a color channel (pin A0 - red channel, pin A1 - green channel, and pin A2 - blue channel). The generated image provides instant feedback of the intensity of the forces and their balance (lighter colours represent higher force value readings, and grey colours represent balanced forces).\n", "The following image shows the schematic of the circuit:\n", "![FSR Color Mixer Circuit](img/force_sensors.png)\n", "\n", "This is the Arduino code:\n", "```cpp\n", "int fsrPin0 = A0; // FSR connected to analog pin A0\n", "int fsrPin1 = A1; // FSR connected to analog pin A1\n", "int fsrPin2 = A2; // FSR connected to analog pin A2\n", "void setup() {\n", " Serial.begin(9600); // Start serial communication at 9600 baud rate\n", "}\n", "void loop() {\n", " int fsrValue0 = analogRead(fsrPin0); // Read FSR value from pin A0\n", " int fsrValue1 = analogRead(fsrPin1); // Read FSR value from pin A1\n", " int fsrValue2 = analogRead(fsrPin2); // Read FSR value from pin A2\n", "\n", " // Send the FSR values over serial in a comma-separated format\n", " Serial.print(fsrValue0);\n", " Serial.print(\",\");\n", " Serial.print(fsrValue1);\n", " Serial.print(\",\");\n", " Serial.println(fsrValue2);\n", "\n", " delay(100); // Wait for 100 milliseconds before the next reading\n", "}\n", "```\n", "\n", "And this is the Python code:\n", "\n", "```python\n", "import time\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import random\n", "from IPython.display import display, clear_output\n", "\n", "\n", "sim_mode = True\n", "\n", "if not sim_mode:\n", " import serial\n", "\n", "def read_fsr_values(ser):\n", " if not sim_mode:\n", " line = ser.readline().decode('utf-8').strip()\n", " values = line.split(\",\")\n", " if len(values) == 3:\n", " return [int(v) for v in values]*255/1023\n", " return None\n", " else:\n", " return [random.randint(0,255) for i in range(3)]\n", "\n", "def main():\n", " if not sim_mode:\n", " ser = serial.Serial('COM3', 9600, timeout=1)\n", " else:\n", " ser = None\n", " time.sleep(2) # Wait for the connection to establish\n", " plt.ion() # Turn on interactive mode\n", " fig, ax = plt.subplots()\n", " img = np.ones((100, 100, 3), dtype=np.uint8)\n", " im = ax.imshow(img)\n", " ax.set_title('FSR Color Mixer')\n", " while True:\n", " fsr_values = read_fsr_values(ser)\n", " if fsr_values is not None:\n", " force_image = np.array(fsr_values, dtype=np.uint8)*np.ones((100, 100, 3), dtype=np.uint8)\n", " im.set_data(force_image)\n", " clear_output(wait=True) # Clear previous output before redrawing\n", " plt.draw() # Redraw the plot internally\n", " display(fig) # Display the updated figure in the output cell\n", " plt.pause(2)\n", " if not sim_mode:\n", " ser.close()\n", "\n", "if __name__ == \"__main__\":\n", " try:\n", " main()\n", " except KeyboardInterrupt:\n", " print(\"\\nStopped.\")\n", "```\n", "Answer the following questions:\n", "1. In the `read_fsr_values` function, there is a bug in the line `return [int(v) for v in values]*255/1023`. Identify and fix the bug. Explain what the corrected line does.\n", "2. If you see the colour ```#E518D6```, which is the analog value read from pin A1 in Arduino?\n", "3. If you see the colour ```#D304B2```, which sensor is providing the highest force reading?\n", "4. Could you scale up or down the number of sensors using the same set-up? Motivate your response" ], "id": "99458136b2e0688b" }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": "", "id": "2f0826af447855bc" }, { "metadata": {}, "cell_type": "markdown", "source": [ "### Distance and direction with ultrasonic sensors\n", "We want to measure distance with two ultrasonic sensors and send the readings to Python. Ultrasonic sensors work as follows: when you send a short pulse (10µS long) pulse to the trigger input, the sensor will send out a 8 cycle burst of ultrasound at 40 kHz and nearby objects will raise its echo. You can calculate the range or distance to the nearby object through the time interval between sending trigger signal and receiving echo signal (the speed of sound is 340 m/s). Note the physical setup in the schematic below. By placing two ultrasonic sensors, separated ~10–20 cm and angled outwards (about 20ª), they work like stereo “ears”, allowing to whether an object is more on the left or on the right of our measurement device. We can detect a small object (hand, book, or cardboard) moved in front at a distance of 20cm to up to 400 cm.\n", "If the object is more on the left, the left sensor tends to read a shorter distance than the right sensor (it “sees it more directly”). If it’s more on the right, the right sensor reads shorter, so by estimating the distance with both sensors and calculating the difference, we can estimate whether the object is to the left or to the right. There are some caveats we need to consider. First, we need to introduce a delay between the left pulse and the right pulse, so that sensors do not perceive each other pulses as echos. Also, we want to avoid noisy data cause flickering between left and right, so we introduce a smoothing average, and a define a *dead band* as an area in the middle where we will not estimate the direction as neither left or right.\n", "\n", "This is the Arduino code:\n", "```cpp\n", "// Stereo Ultrasonic (2x HC-SR04) -> CSV over Serial\n", "// Output format (one line):\n", "// t_ms,dL_cm,dR_cm\n", "//\n", "// Notes:\n", "// - Sensors are triggered sequentially to reduce crosstalk.\n", "// - If no echo is received, distance is reported as -1.\n", "\n", "// Left channel\n", "const int ECHO_L = 7;\n", "const int TRIG_L = 8;\n", "\n", "// Right channel\n", "const int ECHO_R = 9;\n", "const int TRIG_R = 10;\n", "\n", "const unsigned long ECHO_TIMEOUT_US = 30000; // 30 ms timeout (~5 m round-trip)\n", "const int BETWEEN_SENSORS_DELAY_MS = 30; // reduce crosstalk\n", "const int LOOP_DELAY_MS = 100; // overall rate ~10 Hz\n", "\n", "float readDistanceCm(int trigPin, int echoPin) {\n", " // Trigger pulse\n", " pinMode(trigPin, OUTPUT);\n", " digitalWrite(trigPin, LOW);\n", " delayMicroseconds(2);\n", " digitalWrite(trigPin, HIGH);\n", " delayMicroseconds(10);\n", " digitalWrite(trigPin, LOW);\n", "\n", " // Read echo\n", " pinMode(echoPin, INPUT);\n", " unsigned long duration = pulseIn(echoPin, HIGH, ECHO_TIMEOUT_US);\n", " if (duration == 0) return -1.0; // no echo\n", "\n", " // Convert time-of-flight to distance:\n", " // distance(cm) = duration(us) * speed_of_sound(cm/us) / 2\n", " // speed_of_sound ~ 0.0343 cm/us -> /2 => 0.01715\n", " float cm = duration * 0.01715;\n", " return cm;\n", "}\n", "\n", "void setup() {\n", " Serial.begin(9600);\n", " // Optional: give serial time to settle\n", " delay(500);\n", " Serial.println(\"t_ms,dL_cm,dR_cm\"); // header line for easy CSV logging\n", "}\n", "\n", "void loop() {\n", " unsigned long t = millis();\n", "\n", " float dL = readDistanceCm(TRIG_L, ECHO_L);\n", " delay(BETWEEN_SENSORS_DELAY_MS);\n", " float dR = readDistanceCm(TRIG_R, ECHO_R);\n", "\n", " // CSV line\n", " Serial.print(t);\n", " Serial.print(\",\");\n", " Serial.print(dL, 2);\n", " Serial.print(\",\");\n", " Serial.println(dR, 2);\n", "\n", " delay(LOOP_DELAY_MS);\n", "}\n", "```\n", "\n", "And this is the Python code (You can try it in Google Colab or Jupyter Notebooks setting ```sim_mode = True```):\n", "\n", "```python\n", "import csv\n", "import time\n", "import random\n", "\n", "\n", "sim_mode = True\n", "if not sim_mode:\n", " import serial\n", "\n", "# ---------------------------\n", "# Configuration\n", "# ---------------------------\n", "PORT = \"COM3\"\n", "BAUD = 9600\n", "CSV_OUT = \"stereo_ultrasonic_log.csv\"\n", "\n", "MIN_VALID_CM = 2.0\n", "MAX_VALID_CM = 400.0\n", "\n", "DEAD_BAND_CM = 4.0\n", "SMOOTH_N = 3 # moving average window size\n", "\n", "# ---------------------------\n", "def is_valid(d):\n", " return (d >= MIN_VALID_CM) and (d <= MAX_VALID_CM)\n", "\n", "def classify(dL, dR):\n", " \"\"\"\n", " delta = dR - dL\n", " > 0 -> LEFT\n", " < 0 -> RIGHT\n", " \"\"\"\n", " if not (is_valid(dL) and is_valid(dR)):\n", " return \"NO_TARGET\"\n", "\n", " delta = dR - dL\n", " if abs(delta) < DEAD_BAND_CM:\n", " return \"CENTER\"\n", " return \"LEFT\" if delta > 0 else \"RIGHT\"\n", "\n", "def update_buffer(buffer, value, max_len):\n", " \"\"\"\n", " Append value and keep only the last max_len elements.\n", " \"\"\"\n", " buffer.append(value)\n", " if len(buffer) > max_len:\n", " buffer.pop(0)\n", "\n", "\n", "# ---------------------------\n", "def main():\n", " if not sim_mode:\n", " print(f\"Opening serial port {PORT} at {BAUD}...\")\n", " ser = serial.Serial(PORT, BAUD, timeout=1)\n", " time.sleep(2) # let Arduino reset\n", " else:\n", " ser = None\n", " t0 = time.time()\n", "\n", " # Plain Python lists for smoothing\n", " bufL = []\n", " bufR = []\n", "\n", " with open(CSV_OUT, \"w\", newline=\"\") as f:\n", " writer = csv.writer(f)\n", " writer.writerow([\n", " \"pc_time_s\",\n", " \"arduino_t_ms\",\n", " \"dL_cm\",\n", " \"dR_cm\",\n", " \"dL_smooth_cm\",\n", " \"dR_smooth_cm\",\n", " \"delta_smooth_cm\",\n", " \"direction\"\n", " ])\n", "\n", " print(\"Reading... press Ctrl+C to stop.\")\n", " while True:\n", " if not sim_mode:\n", " line = ser.readline().decode(\"utf-8\", errors=\"ignore\").strip()\n", " if not line:\n", " continue\n", "\n", " # Skip header or malformed lines\n", " if line.startswith(\"t_ms\"):\n", " continue\n", "\n", " parts = line.split(\",\")\n", " if len(parts) != 3:\n", " continue\n", "\n", " t_ms = int(parts[0])\n", " dL = float(parts[1])\n", " dR = float(parts[2])\n", " else:\n", " # simulate loop delay\n", " time.sleep(0.1)\n", " t1 = time.time()\n", " t_ms = int((t1-t0)*1000)\n", " dL = random.uniform(2.0, 400.0)\n", " dR = random.uniform(2.0, 400.0)\n", "\n", "\n", " # Update buffers only with valid values\n", " if is_valid(dL):\n", " update_buffer(bufL, dL, SMOOTH_N)\n", " if is_valid(dR):\n", " update_buffer(bufR, dR, SMOOTH_N)\n", "\n", " dL_s = sum(bufL) / len(bufL)\n", " dR_s = sum(bufR) / len(bufR)\n", "\n", " if not (len(bufL) > 0 and len(bufR) > 0):\n", " direction = \"NO_TARGET\"\n", " delta_s = float(\"nan\")\n", " else:\n", " delta_s = dR_s - dL_s\n", " direction = classify(dL_s, dR_s)\n", "\n", " pc_time = time.time()\n", "\n", " writer.writerow([\n", " pc_time,\n", " t_ms,\n", " dL,\n", " dR,\n", " dL_s,\n", " dR_s,\n", " delta_s,\n", " direction\n", " ])\n", " f.flush()\n", "\n", " print(\n", " f\"t={t_ms:6d} ms | \"\n", " f\"dL={dL_s:6.1f} cm dR={dR_s:6.1f} cm | \"\n", " f\"Δ={delta_s:6.1f} -> {direction}\"\n", " )\n", "\n", "# ---------------------------\n", "if __name__ == \"__main__\":\n", " try:\n", " main()\n", " except KeyboardInterrupt:\n", " print(\"\\nStopped.\")\n", "```\n", "\n", "Answer the following questions:\n", "1. Take a look at the Arduino and Python code and write down the header of the csv file ```CSV_OUT```. Motivate your response considering both parts of the application\n", "2. Why is the file mode ```w```? Explain in your own words what changes you would introduce to allow logging distances and directions across different application runs.\n", "3. The left receives an echo pulse with ```duration``` equal to 2400µs. What distance is computed by Arduino? Explain the coefficient used to calculate the distance. Is the value valid for the Python part?\n", "4. Arduino writes the following line in serial ```21000,52.0,55.0```. What is the direction computed by Python?\n", "5. Assume that ```bufL = []``` and ```bufD = []``` The following measurements arrive in order:\n", "\n", "```1000,40.0,50.0```\n", "```1100,42.0,48.0```\n", "```1200,44.0,46.0```\n", "\n", "What are the contents of bufL and bufD after the third sample? Compute ```delta_s``` and direction.\n", "\n", "6. Assume that ```bufL``` and ```bufD``` have the same values after the third sample above. A new sample arrives:\n", "```1300, 46, 44```\n", "\n", "What are the new contents of bufL and bufD? and ```delta_s``` and direction?" ], "id": "302d6080249b9d61" }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": "", "id": "d73d8ec3f9993780" } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 }